package com.iwdnb.gkgz.common.utils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Lists;
import com.iwdnb.bmnf.common.utils.BeanConvertUtils;
import com.iwdnb.bmnf.common.utils.BooleanUtils;
import com.iwdnb.bmnf.common.utils.ClassUtils;
import com.iwdnb.gkgz.common.enums.StockImageProfitTypeEnums;
import com.iwdnb.gkgz.common.enums.StockSecondIndustryEnums;
import com.iwdnb.gkgz.common.model.dto.Stock;
import com.iwdnb.gkgz.common.model.dto.StockDayData;
import com.iwdnb.gkgz.common.model.dto.StockLimitUpData;
import com.iwdnb.gkgz.common.model.dto.StockSupportDTO;
import com.iwdnb.gkgz.common.model.dto.StockSupportDTO.StockSupportCheckInfo;
import com.iwdnb.gkgz.common.model.dto.StockSupportDTO.StockSupportItem;
import com.iwdnb.gkgz.common.model.dto.TldCheckInfo;
import com.iwdnb.gkgz.common.model.dto.TldMatchInfo;
import com.iwdnb.gkgz.common.model.dto.TldMatchInfo.TldMatchItem;
import com.iwdnb.gkgz.common.quota.MovingAverage;
import com.iwdnb.gkgz.common.quota.VolumeRatioIndicator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import static com.iwdnb.gkgz.common.utils.BigDecimalUtils.of;
import static com.iwdnb.gkgz.common.utils.StockUtils.getAveragePrice;
import static com.iwdnb.gkgz.common.utils.StockUtils.getBeforeLimitUpCount;
import static com.iwdnb.gkgz.common.utils.StockUtils.isLimitDown;
import static com.iwdnb.gkgz.common.utils.StockUtils.isLimitUp;
import static com.iwdnb.gkgz.common.utils.StockUtils.isOneLineStock;

@Slf4j
@Component
public class TulongdaoUtils implements ApplicationContextAware {
    private static final int fivePeriod = 5;
    public static final int sevenPeriod = 7;
    private static final int tenPeriod = 10;
    private static final int thirteenPeriod = 13;
    private static final int twentyPeriod = 20;
    private static final int thirtyPeriod = 30;
    private static final int sixtyPeriod = 60;
    public static final List<Integer> ALL_PERIODS = Lists.newArrayList(twentyPeriod, tenPeriod,
        thirtyPeriod, sixtyPeriod);
    public static final List<Integer> TLD_ALL_PERIODS = Lists.newArrayList(tenPeriod, twentyPeriod,
        thirtyPeriod);

    public static final List<Integer> TLD_TWENTY_THIRTY_PERIODS = Lists.newArrayList(twentyPeriod,
        thirtyPeriod);
    //public static final List<Integer> ALL_PERIODS = Lists.newArrayList(twentyPeriod, thirteenPeriod, tenPeriod,
    //    sixtyPeriod, thirtyPeriod);
    private static final Map<Integer, String> PERIOD_MAP = new LinkedHashMap<>();

    private static List<String> winStockImageList;
    private static List<String> allWinStockImageList;
    private static List<String> lossStockImageList;

    public static boolean isLossImageProfitType(String code, List<StockDayData> stockDayDataList, StockDayData data) {
        //计算股价图形特征
        int stockImageValue = StockRangeUtils.cacullateStockImage(stockDayDataList, data);
        return isLossImageProfitType(code, data.getDate(), stockImageValue);
    }

    public static boolean isLossImageProfitType(String code, String date, int stockImageValue) {
        //计算股价图形特征
        String stockImageProfitType = TulongdaoUtils.getStockImageProfitType(stockImageValue);
        //跳过亏损形态
        if (StockImageProfitTypeEnums.LOSS.equalsCode(stockImageProfitType)
            || StockImageProfitTypeEnums.ALL_LOSS.equalsCode(stockImageProfitType)) {
            log.info("{}-{}-stockImageProfitType is loss:{}", code, date, stockImageValue);
            return true;
        }
        return false;
    }

    public static String getStockImageProfitType(int stockImageValue) {
        String v = stockImageValue + "";
        if (winStockImageList.contains(v)) {
            return "win";
        }
        if (allWinStockImageList.contains(v)) {
            return "allWin";
        }
        if (lossStockImageList.contains(v)) {
            return "loss";
        }
        return "balance";
    }

    /**
     * 获取涨停日后的买点日期
     *
     * @param stockDayDataList
     * @param data
     * @return
     */
    public static StockDayData getSignalData(String code, List<StockDayData> stockDayDataList, StockDayData signalData,
        String earlyWarnFlag) {
        BigDecimal beforeClosePrice = stockDayDataList.get(0).getClosePrice();
        int size = stockDayDataList.size();
        int index = 0;
        TldMatchInfo matchInfo = null;
        BigDecimal limitMinPrice = signalData.getMinPrice();
        if (size < 2) {
            return null;
        }
        //-------校验第一天----------
        StockDayData data = stockDayDataList.get(0);
        if (isLimitDown(data) || isLimitUp(data)) {
            return null;
        }
        //第一天涨超过7个点的忽略
        if (BigDecimalUtils.isGe(data.getRate(), BigDecimalUtils.seven)) {
            return null;
        }
        //-------校验第二天----------
        beforeClosePrice = data.getClosePrice();
        index = 1;
        data = stockDayDataList.get(index);
        matchInfo = getTldMatchInfo(code, data, beforeClosePrice, limitMinPrice, index, earlyWarnFlag);
        //只有2天，不满足条件，返回空
        if (Objects.isNull(matchInfo) && size > 2) {
            //-------校验第三天----------
            //把第二天的极端情况干掉
            if (isLimitDown(data) || isLimitUp(data)) {
                return null;
            }
            //收盘价不能跌破涨停日的最低价
            if (BigDecimalUtils.isLt(data.getClosePrice(), signalData.getMinPrice())) {
                return null;
            }
            beforeClosePrice = data.getClosePrice();
            index = 2;
            data = stockDayDataList.get(index);
            matchInfo = getTldMatchInfo(code, data, beforeClosePrice, limitMinPrice, index, earlyWarnFlag);
            //第三天，不满足条件，返回空

        }
        if (Objects.isNull(matchInfo)) {
            return null;
        }
        //for (int i = 0; i < stockDayDataList.size(); i++) {
        //    StockDayData d = stockDayDataList.get(i);
        //    //if (isLimitDown(d)) {
        //    //    return null;
        //    //}
        //    if (i == 0) {
        //        if (isLimitDown(d) || isLimitUp(d)) {
        //            return null;
        //        }
        //    }
        //    //第一天涨超过7个点的忽略
        //    if (i == 0 && BigDecimalUtils.isGe(d.getRate(), seven)) {
        //        return null;
        //    }
        //    //跳过第一天
        //    if (i == 0) {
        //        continue;
        //    }
        //    //跌停板，跳过
        //    //BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(beforeClosePrice, d.getMinPrice());
        //    //if (BigDecimalUtils.isGe(rate, ninePointSix)) {
        //    //    log.info("{}-{}-跌停,跳过", code, d.getDate());
        //    //    continue;
        //    //}
        //    periodList = getAvailablePeriods(d, d.getOpenPrice());
        //    openMatchItemList = getStockLineTldMatchItem(code, d, d.getOpenPrice(), beforeClosePrice, periodList, i,
        //        earlyWarnFlag);
        //    matchItemList = getStockLineTldMatchItem(code, d, d.getMinPrice(), beforeClosePrice, periodList, i,
        //        earlyWarnFlag);
        //    crossItemList = getCrossPeriods(d, d.getOpenPrice(), d.getMinPrice(), beforeClosePrice, periodList);
        //    matchItem = getMatchItem(openMatchItemList, matchItemList, crossItemList, d.getOpenPrice());
        //    if (Objects.nonNull(matchItem)) {
        //        match = d;
        //        matchIndex = i;
        //        break;
        //    }
        //    beforeClosePrice = d.getClosePrice();
        //}
        //if (Objects.isNull(match)) {
        //    return null;
        //}
        ////跌停板,不是买入点，TODO 这个有问题，跌停板会在实际过程中发生，历史数据能直接判定过滤掉。
        //if (isLimitDown(match)) {
        //    return null;
        //}
        //收盘价不能跌破涨停日的最低价,TODO 这个有问题，跌停板会在实际过程中发生，历史数据能直接判定过滤掉。
        //if (BigDecimalUtils.isLt(match.getClosePrice(), data.getMinPrice())) {
        //    return null;
        //}
        //统计数据：第3天跌超过6.5的胜率不高,TODO 这个有问题，跌停板会在实际过程中发生，历史数据能直接判定过滤掉。
        //if (matchIndex == 2) {
        //    StockDayData beforeData = stockDayDataList.get(matchIndex - 1);
        //    BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(beforeData.getClosePrice(),
        //        match.getClosePrice());
        //    if (BigDecimalUtils.isGe(rate, sixPointFive)) {
        //        return null;
        //    }
        //}
        data.setTldMatchInfo(matchInfo);
        return data;
    }

    private static TldMatchInfo getTldMatchInfo(String code, StockDayData data, BigDecimal beforeClosePrice,
        BigDecimal limitMinPrice, int index,
        String earlyWarnFlag) {
        List<Integer> periodList = getAvailablePeriods(data, data.getOpenPrice());
        List<TldMatchItem> openMatchItemList = getStockLineTldMatchItem(code, data, data.getOpenPrice(),
            beforeClosePrice, limitMinPrice, periodList, index, earlyWarnFlag);
        if (CollectionUtils.isNotEmpty(openMatchItemList)) {
            openMatchItemList.forEach(item -> {
                item.setOpenMatchFlag(BooleanUtils.TRUE);
            });
        }
        List<TldMatchItem> matchItemList = getStockLineTldMatchItem(code, data, data.getMinPrice(), beforeClosePrice,
            limitMinPrice, periodList, index, earlyWarnFlag);
        List<TldMatchItem> crossItemList = getCrossPeriods(data, data.getOpenPrice(), data.getMinPrice(),
            beforeClosePrice, periodList);
        TldMatchItem matchItem = getMatchItem(openMatchItemList, matchItemList, crossItemList, data.getOpenPrice());
        if (Objects.isNull(matchItem)) {
            return null;
        }
        TldMatchInfo matchInfo = new TldMatchInfo();
        matchInfo.setCode(code);
        matchInfo.setIndex(index);
        matchInfo.setOpenMatchItems(openMatchItemList);
        matchInfo.setMatchItems(matchItemList);
        matchInfo.setCrossItems(crossItemList);
        matchInfo.setMatchItem(BeanConvertUtils.convert(matchItem, TldMatchItem.class));
        matchInfo.setMatchDate(
            StringUtils.isNotBlank(data.getDatetime()) ? data.getDatetime() : data.getDate() + " 00:00:00");
        matchInfo.setOpenPrice(data.getOpenPrice());
        matchInfo.setEarlyWarnFlag(earlyWarnFlag);
        return matchInfo;
    }

    /**
     * 获取可用的均线
     *
     * @param data
     * @param openPrice
     * @return
     */
    private static List<Integer> getAvailablePeriods(StockDayData data, BigDecimal openPrice) {
        List<Integer> periods = new ArrayList<>();
        BigDecimal comparePrice = BigDecimalUtils.multiply(openPrice, BigDecimalUtils.onePointZeroOneTwo);
        for (Integer period : TLD_ALL_PERIODS) {
            BigDecimal avg = getFixedAveragePrice(data, period);
            //均线在开盘价之下的，才符合标准
            if (BigDecimalUtils.isLe(avg, comparePrice)) {
                periods.add(period);
            }
        }
        return periods;
    }

    private static List<TldMatchItem> getStockLineTldMatchItem(String code, StockDayData data, BigDecimal price,
        BigDecimal beforeClosePrice, BigDecimal limitMinPrice, List<Integer> periodList, int index,
        String earlyWarnFlag) {
        if (CollectionUtils.isEmpty(periodList)) {
            return null;
        }
        //价格比昨日收盘价高1个点以上，直接不匹配
        BigDecimal itemBeforeCloseRate = BigDecimalUtils.subStractAndDividePrecent(price, beforeClosePrice);
        if (BigDecimalUtils.isGt(itemBeforeCloseRate, BigDecimalUtils.one)) {
            return null;
        }
        List<TldMatchItem> matchInfoList = new ArrayList<>();
        Integer maxPeriod = 10;
        BigDecimal maxAvg = getFixedAveragePrice(data, maxPeriod);
        BigDecimal compareRate = BooleanUtils.isTrue(earlyWarnFlag) ? BigDecimalUtils.twoPointTwoFive
            : BigDecimalUtils.onePointTwoFive;
        BigDecimal specialCompareRate = BooleanUtils.isTrue(earlyWarnFlag) ? BigDecimalUtils.three
            : BigDecimalUtils.twoPointTwoFive;
        for (Integer period : periodList) {
            BigDecimal avg = getFixedAveragePrice(data, period);
            if (BigDecimalUtils.isGt(avg, maxAvg)) {
                maxPeriod = period;
                maxAvg = avg;
            }
            BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(price, avg);
            //匹配均线
            if (BigDecimalUtils.isGt(rate.abs(), compareRate)) {
                continue;
            }
            //买入价不能低于涨停日最低价
            BigDecimal buyPrice = BigDecimalUtils.multiply(avg, BigDecimalUtils.onePointZeroOneTwo, 4);
            if (BigDecimalUtils.isLt(buyPrice, limitMinPrice)) {
                continue;
            }
            TldMatchItem matchInfo = new TldMatchItem();
            matchInfo.setPeriod(period);
            matchInfo.setPrice(price);
            matchInfo.setAvg(avg);
            matchInfo.setRate(rate);
            matchInfo.setBuyPrice(buyPrice);
            matchInfoList.add(matchInfo);
        }
        ////特殊处理，第3天最高的10日均线与昨日收盘价差离超过5%，且股价跌落到10日均线2个点以内，则认为是买点
        //if (CollectionUtils.isEmpty(matchInfoList) && index == 2) {
        //    BigDecimal downRate = BigDecimalUtils.subStractAndDividePrecent(beforeClosePrice, maxAvg);
        //    if (BigDecimalUtils.isGe(downRate, five) && maxPeriod == 10) {
        //        //匹配10日均线2个点范围内
        //        BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(price, maxAvg);
        //        if (BigDecimalUtils.isLe(rate.abs(), specialCompareRate)) {
        //            TldMatchItem matchInfo = new TldMatchItem();
        //            matchInfo.setPeriod(-1);
        //            matchInfo.setPrice(price);
        //            matchInfo.setAvg(maxAvg);
        //            matchInfo.setRate(rate);
        //            matchInfo.setBuyPrice(price);
        //            matchInfoList.add(matchInfo);
        //            log.info("{}-{}:tulongdao special match tenAvg 2% signal:{}", code, data.getDate(), matchInfo);
        //        }
        //    }
        //}
        return matchInfoList;
    }

    /**
     * 获取涨停日后的买点日期
     *
     * @param stockDayDataList
     * @param data
     * @return
     */
    public static StockDayData getSpecialSignalData(String code, List<StockDayData> stockDayDataList,
        StockDayData signalData,
        String earlyWarnFlag) {
        BigDecimal beforeClosePrice = stockDayDataList.get(0).getClosePrice();
        int size = stockDayDataList.size();
        int index = 0;
        TldMatchInfo matchInfo = null;
        BigDecimal limitMinPrice = signalData.getMinPrice();
        if (size < 2) {
            return null;
        }
        //-------校验第一天----------
        StockDayData data = stockDayDataList.get(0);
        if (isLimitDown(data) || isLimitUp(data)) {
            return null;
        }
        //第一天涨超过7个点的忽略
        if (BigDecimalUtils.isGe(data.getRate(), BigDecimalUtils.seven)) {
            return null;
        }
        //-------校验第二天----------
        beforeClosePrice = data.getClosePrice();
        index = 1;
        data = stockDayDataList.get(2);
        matchInfo = getTldMatchInfo(code, data, beforeClosePrice, limitMinPrice, index, earlyWarnFlag);
        //第二天匹配普通屠龙刀，则不去匹配特殊屠龙刀
        if (Objects.nonNull(matchInfo)) {
            return null;
        }
        //只有2天，不满足条件，返回空
        if (Objects.isNull(matchInfo) && size > 2) {
            //-------校验第三天----------
            //把第二天的极端情况干掉
            if (isLimitDown(data) || isLimitUp(data)) {
                return null;
            }
            //收盘价不能跌破涨停日的最低价
            if (BigDecimalUtils.isLt(data.getClosePrice(), signalData.getMinPrice())) {
                return null;
            }
            beforeClosePrice = data.getClosePrice();
            index = 2;
            data = stockDayDataList.get(index);
            //第三天特殊校验
            TldMatchItem matchItem = getSpecialStockLineTldMatchItem(code, data, data.getMinPrice(), beforeClosePrice,
                earlyWarnFlag);
            if (Objects.isNull(matchItem)) {
                return null;
            }
            matchInfo = new TldMatchInfo();
            matchInfo.setCode(code);
            matchInfo.setIndex(index);
            matchInfo.setMatchItems(Lists.newArrayList(matchItem));
            matchInfo.setMatchItem(BeanConvertUtils.convert(matchItem, TldMatchItem.class));
            matchInfo.setMatchDate(
                StringUtils.isNotBlank(data.getDatetime()) ? data.getDatetime() : data.getDate() + " 00:00:00");
            matchInfo.setOpenPrice(data.getOpenPrice());
            matchInfo.setEarlyWarnFlag(earlyWarnFlag);
        }
        if (Objects.isNull(matchInfo)) {
            return null;
        }

        data.setTldMatchInfo(matchInfo);
        return data;
    }

    private static TldMatchItem getSpecialStockLineTldMatchItem(String code, StockDayData data, BigDecimal price,
        BigDecimal beforeClosePrice, String earlyWarnFlag) {

        //价格比昨日收盘价高1个点以上，直接不匹配
        BigDecimal itemBeforeCloseRate = BigDecimalUtils.subStractAndDividePrecent(price, beforeClosePrice);
        if (BigDecimalUtils.isGt(itemBeforeCloseRate, BigDecimalUtils.one)) {
            return null;
        }
        List<TldMatchItem> matchInfoList = new ArrayList<>();
        Integer maxPeriod = 10;
        BigDecimal maxAvg = getFixedAveragePrice(data, maxPeriod);
        BigDecimal specialCompareRate = BooleanUtils.isTrue(earlyWarnFlag) ? BigDecimalUtils.three
            : BigDecimalUtils.twoPointTwoFive;

        //特殊处理，第3天最高的10日均线与昨日收盘价差离超过5%，且股价跌落到10日均线2个点以内，则认为是买点
        if (CollectionUtils.isEmpty(matchInfoList)) {
            BigDecimal downRate = BigDecimalUtils.subStractAndDividePrecent(beforeClosePrice, maxAvg);
            if (BigDecimalUtils.isGe(downRate, BigDecimalUtils.five) && maxPeriod == 10) {
                //匹配10日均线2个点范围内
                BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(price, maxAvg);
                if (BigDecimalUtils.isLe(rate.abs(), specialCompareRate)) {
                    TldMatchItem matchInfo = new TldMatchItem();
                    matchInfo.setPeriod(-1);
                    matchInfo.setPrice(price);
                    matchInfo.setAvg(maxAvg);
                    matchInfo.setRate(rate);
                    matchInfo.setBuyPrice(price);
                    log.info("{}-{}:tulongdao special match tenAvg 2% signal:{}", code, data.getDate(), matchInfo);
                    return matchInfo;
                }
            }
        }
        return null;
    }

    private static BigDecimal getFixedAveragePrice(StockDayData data, Integer period) {
        BigDecimal avg = getAveragePrice(data, period);
        //计算差价平均,与均线相减
        BigDecimal priceRange = BigDecimalUtils.subtract(data.getClosePrice(), data.getMinPrice());
        BigDecimal avgRange = BigDecimalUtils.divide(priceRange, new BigDecimal(period.toString()), 4);
        return BigDecimalUtils.subtract(avg, avgRange, 4);
    }

    private static List<TldMatchItem> getCrossPeriods(StockDayData data, BigDecimal openPrice, BigDecimal minPrice,
        BigDecimal beforeClosePrice, List<Integer> periodList) {
        if (CollectionUtils.isEmpty(periodList)) {
            return null;
        }
        List<TldMatchItem> matchItemList = new ArrayList<>();
        for (Integer period : periodList) {
            BigDecimal avg = getFixedAveragePrice(data, period);
            BigDecimal upPrice = BigDecimalUtils.multiply(openPrice, BigDecimalUtils.onePointZeroOneTwo);
            BigDecimal downPrice = BigDecimalUtils.multiply(minPrice, BigDecimalUtils.zeroPointNineEightEight);
            if (BigDecimalUtils.isLe(avg, upPrice) && BigDecimalUtils.isGe(avg, downPrice)) {
                //价格比昨日收盘价高1个点以上，直接不匹配
                BigDecimal itemBeforeCloseRate = BigDecimalUtils.subStractAndDividePrecent(avg, beforeClosePrice);
                if (BigDecimalUtils.isGt(itemBeforeCloseRate, BigDecimalUtils.one)) {
                    continue;
                }
                TldMatchItem matchInfo = new TldMatchItem();
                matchInfo.setPeriod(period);
                matchInfo.setAvg(avg);
                matchInfo.setRate(BigDecimalUtils.onePointZeroOneTwo);
                matchInfo.setBuyPrice(BigDecimalUtils.multiply(avg, BigDecimalUtils.onePointZeroOneTwo, 4));
                matchInfo.setPrice(avg);
                matchItemList.add(matchInfo);
            }

        }
        return matchItemList;
    }

    private static List<Integer> getStockLineEqualInfo(StockDayData data) {
        BigDecimal currentMinPrice = data.getMinPrice();
        List<Integer> periods = new ArrayList<>();
        for (Entry<Integer, String> entry : PERIOD_MAP.entrySet()) {
            Integer period = entry.getKey();
            if (BigDecimalUtils.eq(currentMinPrice, getAveragePrice(data, period))) {
                periods.add(period);
            }
        }
        return periods;
    }

    public static TldMatchItem getMatchItem(TldMatchInfo matchInfo) {
        return getMatchItem(matchInfo.getOpenMatchItems(), matchInfo.getMatchItems(), matchInfo.getCrossItems(),
            matchInfo.getOpenPrice());
    }

    public static TldMatchItem getMatchItem(List<TldMatchItem> openMatchItems, List<TldMatchItem> matchItems,
        List<TldMatchItem> crossItems, BigDecimal openPrice) {
        if (CollectionUtils.isNotEmpty(openMatchItems)) {
            sortItemList(openMatchItems);
            for (TldMatchItem item : openMatchItems) {
                if (BigDecimalUtils.isLe(item.getAvg(), openPrice) && item.getPeriod() != -1) {
                    return item;
                }
            }
        }
        //穿过的均线匹配
        if (CollectionUtils.isNotEmpty(crossItems)) {
            sortItemList(crossItems);
            for (TldMatchItem item : crossItems) {
                if (BigDecimalUtils.isLt(item.getAvg(), openPrice)) {
                    return item;
                }
            }
        }
        //匹配最低价
        if (CollectionUtils.isNotEmpty(matchItems)) {
            sortItemList(matchItems);
            for (TldMatchItem item : matchItems) {
                if (BigDecimalUtils.isLt(item.getAvg(), openPrice)) {
                    return item;
                }
            }
        }
        return null;
    }

    private static void sortItemList(List<TldMatchItem> matchItems) {
        matchItems.sort((o1, o2) -> o2.getAvg().compareTo(o1.getAvg()));
    }

    public static TldCheckInfo checkTldMacthInfo(String code, List<StockDayData> stockDayDataList,
        String signalDateStr) {
        TldCheckInfo checkInfo = new TldCheckInfo();
        checkInfo.setCode(code);
        Stock stock = StockUtils.getStock(code);
        if (StockSecondIndustryEnums.isBlackIndustry(stock.getSecondIndustry())) {
            log.info("{} is isBlackIndustry:{},ignore", code, stock.getSecondIndustry());
            return null;
        }
        if (CollectionUtils.isEmpty(stockDayDataList)) {
            return null;
        }
        StockDayData data = StockUtils.getStockDayData(stockDayDataList, signalDateStr);
        //跳过非涨停或者一字板
        if (!isLimitUp(data) || isOneLineStock(data)) {
            return null;
        }
        Date signalDate = DateUtil.parseDate(signalDateStr);
        List<StockDayData> beforeDayDatas = StockUtils.getStockDayDataListBeforeDate(stockDayDataList, signalDateStr,
            200);
        StockDayData beforeOneData = beforeDayDatas.get(beforeDayDatas.size() - 1);
        StockDayData beforeTwoData = beforeDayDatas.get(beforeDayDatas.size() - 2);
        StockDayData beforeThreeData = beforeDayDatas.get(beforeDayDatas.size() - 3);
        if (BigDecimalUtils.isLe(data.getOpenPrice(), BigDecimal.ONE)) {
            return null;
        }
        String date = data.getDate();
        if (!isLimitUp(data)) {
            return null;
        }
        if (isOneLineStock(data)) {
            return null;
        }
        //跳过亏损形态
        int stockImageValue = StockRangeUtils.cacullateStockImage(stockDayDataList, data);
        if (TulongdaoUtils.isLossImageProfitType(code, date, stockImageValue)) {
            //log.info("{}-{}-stockImageProfitType is loss", code, date);
            return null;
        }
        //股票底部的支撑价格
        BigDecimal limitPrice = BigDecimalUtils.isGe(data.getOpenPrice(), beforeOneData.getClosePrice())
            ? beforeOneData.getClosePrice() : data.getOpenPrice();
        //不是前俩板涨停，跳过
        int beforeLimitUpCount = getBeforeLimitUpCount(data, stockDayDataList, 3);
        if (beforeLimitUpCount == 0 || beforeLimitUpCount > 2) {
            return null;
        }
        //20日内涨停次数
        int beforeTwentyLimitUpCount = getBeforeLimitUpCount(data, stockDayDataList, 20);
        //if (beforeLimitUpCount == 0 || beforeLimitUpCount > 2) {
        //    return null;
        //}
        int afterDay = 3;
        List<StockDayData> afterDataList = StockUtils.getStockDayDataListAfterDate(stockDayDataList, date,
            afterDay);
        if (CollectionUtils.isEmpty(afterDataList) || afterDataList.size() < 2) {
            //log.debug("{}-{}-未找到未来2天的数据，跳过", code, date);
            return null;
        }
        StockDayData afterOneData = afterDataList.get(0);
        StockDayData afterTwoData = afterDataList.get(1);
        StockDayData afterThreeData = afterDataList.size() >= 3 ? afterDataList.get(2) : null;
        BigDecimal beforeTwoVolumeRatio = StockUtils.getStockQuota(beforeTwoData, VolumeRatioIndicator.QUOTA_NAME);
        BigDecimal beforeOneVolumeRatio = StockUtils.getStockQuota(beforeOneData, VolumeRatioIndicator.QUOTA_NAME);
        BigDecimal dataVolumeRatio = StockUtils.getStockQuota(data, VolumeRatioIndicator.QUOTA_NAME);
        BigDecimal afterOneVolumeRatio = StockUtils.getStockQuota(afterOneData, VolumeRatioIndicator.QUOTA_NAME);
        BigDecimal afterTwoVolumeRatio = StockUtils.getStockQuota(afterTwoData, VolumeRatioIndicator.QUOTA_NAME);

        //统计结果:涨停后一日最高价低于涨停价0.5%,且跌幅小于4.5%,单次盈利小于1%，跳过
        BigDecimal afterOneMaxPriceRate = BigDecimalUtils.subStractAndDividePrecent(afterOneData.getMaxPrice(),
            data.getClosePrice());
        BigDecimal afterOneRangeRate = afterOneData.getRate();
        if (BigDecimalUtils.isLt(afterOneMaxPriceRate, BigDecimalUtils.fuZeroPointFive) && BigDecimalUtils.isGt(
            afterOneRangeRate, BigDecimalUtils.fuFourPointEight)) {
            //return null;
        }

        //股价与40天内最低价相差200%以内
        StockDayData beforeFourtyLowData = StockUtils.getMinPriceDataBeforeDate(stockDayDataList, date, 40);
        BigDecimal beforeFourtyLowPrice = beforeFourtyLowData.getMinPrice();
        BigDecimal beforeFourtyLowRate = BigDecimalUtils.subStractAndDividePrecent(data.getClosePrice(),
            beforeFourtyLowPrice);
        if (BigDecimalUtils.isGe(beforeFourtyLowRate, BigDecimalUtils.twoHundred)) {
            return null;
        }

        //获取涨停后的买入点
        StockDayData matchData = null;
        if (beforeLimitUpCount > 1) {
            //afterLowData = getTwoLimitUpAfterLowData(afterDataList, beforeOneData);
            return null;
        }
        //忽略50日内出过3连板以上的
        List<StockDayData> beforeFiftyDataList = StockUtils.getStockDayDataListBeforeDate(stockDayDataList, date,
            50);
        int beforeFiftyMultyLimitUpCount = StockUtils.getMultiLimitUpCount(stockDayDataList);
        int beforeFiftyLimitUpCount = StockUtils.getLimitUpCount(stockDayDataList);
        //5天内最高价小于15%
        BigDecimal beforeFiveHighPrice = StockUtils.getMaxPriceBeforeDate(stockDayDataList, date, 10);
        BigDecimal beforeFiveHighRate = BigDecimalUtils.subStractAndDividePrecent(beforeFiveHighPrice,
            data.getClosePrice());
        //if (BigDecimalUtils.isGe(beforeFiveHighRate, BigDecimalUtils.fifteen)) {
        //    continue;
        //}
        //20天内涨幅小于40%
        StockDayData beforeTwentyLowData = StockUtils.getMinPriceDataBeforeDate(stockDayDataList, date, 20);
        BigDecimal beforeTwentyLowPrice = beforeTwentyLowData.getMinPrice();
        StockDayData beforeTwentyHighData = StockUtils.getMaxPriceDataBeforeDate(stockDayDataList, date, 20);
        BigDecimal beforeTwentyHighPrice = beforeTwentyHighData.getMaxPrice();

        BigDecimal beforeTwentyRate = BigDecimalUtils.subStractAndDividePrecent(beforeTwentyHighPrice,
            beforeTwentyLowPrice);
        if (DateUtils.before(DateUtil.parseDate(beforeTwentyHighData.getDate()),
            DateUtil.parseDate(beforeTwentyLowData.getDate()))) {
            beforeTwentyRate = BigDecimalUtils.subStractAndDividePrecent(beforeTwentyLowPrice, beforeTwentyHighPrice);
        }
        BigDecimal afterOneUplineRate = StockUtils.caculateUpLineRate(afterOneData);
        ////统计结果:涨停次日上影线长度极短(0.3%以内),且买入价比涨停板低6.8%以内，单次盈利小于1.41%，跳过
        //if (BigDecimalUtils.isLe(afterOneUplineRate, BigDecimalUtils.zeroPointThree)
        //    && BigDecimalUtils.isLe(buyRate, BigDecimalUtils.sixPointEight)) {
        //    //return null;
        //}
        //if (BigDecimalUtils.isGe(beforeTwentyRate, BigDecimalUtils.fourty)) {
        //    continue;
        //}
        matchData = TulongdaoUtils.getSignalData(code, afterDataList, data, BooleanUtils.FALSE);
        if (Objects.isNull(matchData)) {
            return null;
        }
        TldMatchItem tldMatchItem = matchData.getTldMatchInfo().getMatchItem();
        if (Objects.isNull(tldMatchItem)) {
            return null;
        }
        Integer period = matchData.getTldMatchInfo().getMatchItem().getPeriod();

        //int matchDayIndex = matchData.getTldMatchInfo().getIndex();
        ////买入日跌小于5个点
        //if (BigDecimalUtils.isLe(afterLowData.getRate(), fuFive)) {
        //    continue;
        //}
        //过滤成交量图形比较差的数据
        String tradeNumRatioCode = getTradeNumRatioCode(beforeTwoData, beforeOneData, data, afterOneData, afterTwoData,
            matchData.getTldMatchInfo().getIndex());
        if (TradeNumRatioUtils.isBlack(tradeNumRatioCode)) {
            return null;
        }
        //统计结果:涨停前一日量比小于0.5，盈利为:13/119=0.114，跳过
        if (BigDecimalUtils.isLe(beforeOneVolumeRatio, BigDecimalUtils.zeroPointFive)) {
            return null;
        }
        //统计结果:涨停日量比是前一日的3.5倍以上，盈利为312/307=1.02,跳过
        BigDecimal volumeRatioRate = BigDecimalUtils.divide(dataVolumeRatio, beforeOneVolumeRatio);
        if (BigDecimalUtils.isGt(volumeRatioRate, BigDecimalUtils.threePointFive)) {
            return null;
        }
        //买入均线为20、30日均线，买入价为涨停价差<3.5,盈利为371/792=0.47,跳过
        BigDecimal buyRate = BigDecimalUtils.subStractAndDividePrecent(data.getClosePrice(),
            tldMatchItem.getBuyPrice());
        if ((period == 20 || period == 30) &&
            BigDecimalUtils.isLe(buyRate, BigDecimalUtils.threePointFive)) {
            return null;
        }
        //统计结果：买入均线为20日均线，买入点价格比涨停板低超过9.6%,盈利为4.4/40=0.11,跳过
        if (period == 20 && BigDecimalUtils.isGt(buyRate, BigDecimalUtils.ninePointSix)) {
            return null;
        }
        //统计结果:涨停次日与涨停前三日的10日均线差值<0%，60日均线差值介于-2%~0%,225/513=0.44，跳过
        BigDecimal avgRate10 = BigDecimalUtils.subStractAndDividePrecent(
            afterOneData.getAvarage().getBigDecimal("10"), beforeThreeData.getAvarage().getBigDecimal("10"));
        BigDecimal avgRate20 = BigDecimalUtils.subStractAndDividePrecent(
            afterOneData.getAvarage().getBigDecimal("20"), beforeThreeData.getAvarage().getBigDecimal("20"));
        BigDecimal avgRate30 = BigDecimalUtils.subStractAndDividePrecent(
            afterOneData.getAvarage().getBigDecimal("30"), beforeThreeData.getAvarage().getBigDecimal("30"));
        BigDecimal avgRate60 = BigDecimalUtils.subStractAndDividePrecent(
            afterOneData.getAvarage().getBigDecimal("60"), beforeThreeData.getAvarage().getBigDecimal("60"));
        if (BigDecimalUtils.isLt(avgRate10, BigDecimal.ZERO)
            && BigDecimalUtils.isGt(avgRate60, BigDecimalUtils.fuTwo)
            && BigDecimalUtils.isLt(avgRate60, BigDecimal.ZERO)) {
            return null;
        }
        ////统计结果:涨停次日与涨停前三日的10日均线差值<0%，60日均线差值<0%,盈利为88/126=0.7，跳过
        //BigDecimal afterTwoAvgRate10 = BigDecimalUtils.subStractAndDividePrecent(
        //    afterTwoData.getAvarage().getBigDecimal("10"), beforeTwoData.getAvarage().getBigDecimal("10"));
        //BigDecimal afterTwoAvgRate20 = BigDecimalUtils.subStractAndDividePrecent(
        //    afterTwoData.getAvarage().getBigDecimal("20"), beforeTwoData.getAvarage().getBigDecimal("20"));
        //BigDecimal afterTwoAvgRate30 = BigDecimalUtils.subStractAndDividePrecent(
        //    afterTwoData.getAvarage().getBigDecimal("30"), beforeTwoData.getAvarage().getBigDecimal("30"));
        //BigDecimal afterTwoAvgRate60 = BigDecimalUtils.subStractAndDividePrecent(
        //    afterTwoData.getAvarage().getBigDecimal("60"), beforeTwoData.getAvarage().getBigDecimal("60"));
        //if (BigDecimalUtils.isLt(avgRate10, BigDecimal.ZERO) && BigDecimalUtils.isLt(avgRate60, BigDecimal.ZERO)) {
        //    //return null;
        //}
        ////统计结果:涨停次日与涨停前三日的10日均线差值<0%，60日均线差值<0%,单次盈利小于1.1%，跳过
        BigDecimal buyAvgRate10 = BigDecimalUtils.subStractAndDividePrecent(
            getFixedAveragePrice(matchData, 10), tldMatchItem.getPrice());
        BigDecimal buyAvgRate20 = BigDecimalUtils.subStractAndDividePrecent(
            getFixedAveragePrice(matchData, 20), tldMatchItem.getPrice());
        BigDecimal buyAvgRate30 = BigDecimalUtils.subStractAndDividePrecent(
            getFixedAveragePrice(matchData, 30), tldMatchItem.getPrice());
        BigDecimal buyAvgRate60 = BigDecimalUtils.subStractAndDividePrecent(
            getFixedAveragePrice(matchData, 60), tldMatchItem.getPrice());
        //if (BigDecimalUtils.isLt(avgRate10, BigDecimal.ZERO) && BigDecimalUtils.isLt(avgRate60, BigDecimal.ZERO)) {
        //    //return null;
        //}
        ////统计结果:买入均线为20日均线，且20日均线在10日均线上方,盈利为:202/257=0.79,跳过
        //BigDecimal avg10 = matchData.getAvarage().getBigDecimal("10");
        //BigDecimal avg20 = matchData.getAvarage().getBigDecimal("20");
        //if (period == 20 && BigDecimalUtils.isGt(avg20, avg10)) {
        //    //return null;
        //}
        ////统计结果:买入均线为20,30日均线，且涨停次日涨幅为-2~1之间,盈利为:95/124=0.77,跳过
        //if (TLD_TWENTY_THIRTY_PERIODS.contains(period)
        //    && BigDecimalUtils.isGt(afterOneRangeRate, BigDecimalUtils.fuTwo)
        //    && BigDecimalUtils.isLt(afterOneRangeRate, BigDecimalUtils.one)) {
        //    //return null;
        //}
        //统计结果:涨停次日10日均线涨幅>6%,20日均线差值>6%,盈利为:96/100=0.96,跳过
        if (BigDecimalUtils.isGt(avgRate10, BigDecimalUtils.six)
            && BigDecimalUtils.isGt(avgRate20, BigDecimalUtils.six)) {
            return null;
        }
        //统计结果:买入均线为10日均线，涨停日上影线<0.5,买入价比涨停价低7%以上,盈利为:130/178=0.73,跳过
        if (period == 10
            && BigDecimalUtils.isLt(afterOneUplineRate, BigDecimalUtils.zeroPointFive)
            && BigDecimalUtils.isGt(buyRate, BigDecimalUtils.seven)) {
            return null;
        }
        checkInfo.setSignalData(data);
        checkInfo.setBuyRate(buyRate);
        checkInfo.setBeforeOneData(beforeOneData);
        checkInfo.setBeforeTwoData(beforeTwoData);
        checkInfo.setBeforeThreeData(beforeThreeData);
        checkInfo.setAfterOneData(afterOneData);
        checkInfo.setAfterTwoData(afterTwoData);
        checkInfo.setAfterThreeData(afterThreeData);
        checkInfo.setMatchData(matchData);
        checkInfo.setMatchInfo(matchData.getTldMatchInfo());
        checkInfo.setDataMinPriceRate(
            BigDecimalUtils.subStractAndDividePrecent(data.getMinPrice(), beforeOneData.getClosePrice()));
        checkInfo.setLimitPrice(limitPrice);
        checkInfo.setBeforeTwoVolumeRatio(beforeTwoVolumeRatio);
        checkInfo.setBeforeOneVolumeRatio(beforeOneVolumeRatio);
        checkInfo.setDataVolumeRatio(dataVolumeRatio);
        checkInfo.setAfterOneVolumeRatio(afterOneVolumeRatio);
        checkInfo.setAfterTwoVolumeRatio(afterTwoVolumeRatio);
        checkInfo.setAvgRate10(avgRate10);
        checkInfo.setAvgRate20(avgRate20);
        checkInfo.setAvgRate30(avgRate30);
        checkInfo.setAvgRate60(avgRate60);
        //checkInfo.setAfterTwoAvgRate10(afterTwoAvgRate10);
        //checkInfo.setAfterTwoAvgRate20(afterTwoAvgRate20);
        //checkInfo.setAfterTwoAvgRate30(afterTwoAvgRate30);
        //checkInfo.setAfterTwoAvgRate60(afterTwoAvgRate60);
        checkInfo.setAfterOneUplineRate(afterOneUplineRate);
        checkInfo.setAfterOneMaxPriceRate(afterOneMaxPriceRate);
        checkInfo.setAfterOneOpenCloseRate(BigDecimalUtils.subStractAndDividePrecent(afterOneData.getOpenPrice(),
            afterOneData.getClosePrice()));
        checkInfo.setStockImageValue(stockImageValue);
        checkInfo.setTradeNumRatioCode(tradeNumRatioCode);
        checkInfo.setBeforeTwentyLimitUpCount(beforeTwentyLimitUpCount);
        checkInfo.setBeforeTwentyRate(beforeTwentyRate);
        checkInfo.setBuyAvgRate10(buyAvgRate10);
        checkInfo.setBuyAvgRate20(buyAvgRate20);
        checkInfo.setBuyAvgRate30(buyAvgRate30);
        checkInfo.setBuyAvgRate60(buyAvgRate60);
        return checkInfo;
    }

    /**
     * 预测明日股票屠龙刀支撑价格
     *
     * @param closePrice
     * @param code
     * @param stockDayDataList
     * @param limitUpClosePrice
     * @return
     */
    public static StockSupportDTO predictTldSupportPrice(String code, List<StockDayData> stockDayDataList,
        StockLimitUpData limitUpData) {
        String limitUpDate = DateUtil.formatDate(limitUpData.getDate());
        BigDecimal limitUpClosePrice = limitUpData.getClosePrice();
        limitUpClosePrice = BigDecimalUtils.multiply(limitUpClosePrice, BigDecimalUtils.zeroPointNineSixFive);
        StockSupportDTO stockSupportDTO = new StockSupportDTO();
        stockSupportDTO.setCode(code);
        StockDayData latestData = BeanConvertUtils.convert(stockDayDataList.get(stockDayDataList.size() - 1),
            StockDayData.class);
        BigDecimal closePrice = latestData.getClosePrice();
        BigDecimal limitUpPrice = BigDecimalUtils.multiply(closePrice, BigDecimalUtils.onePointOne);
        BigDecimal limitDownPrice = BigDecimalUtils.multiply(closePrice, BigDecimalUtils.zeroPointNine);
        latestData.setOpenPrice(limitUpClosePrice);
        latestData.setClosePrice(limitUpClosePrice);
        latestData.setMaxPrice(limitUpClosePrice);
        latestData.setMinPrice(limitUpClosePrice);
        latestData.setDate(DateUtil.formatDate(WorkDayUtils.getNextWorkDay(DateUtil.parseDate(latestData.getDate()))));
        stockDayDataList = BeanConvertUtils.convertList(stockDayDataList, StockDayData.class);
        stockDayDataList.add(latestData);
        MovingAverage.calculateEMA(stockDayDataList, ALL_PERIODS);
        List<StockSupportItem> supportItems = new ArrayList<>();
        for (Integer period : TLD_ALL_PERIODS) {
            BigDecimal avg = getFixedAveragePrice(latestData, period);
            //均线在涨停板之上，跳过
            if (BigDecimalUtils.isGt(avg, limitUpPrice)) {
                log.error("{}-{} avg isGreatThan limitUpPrice,ignore,avg:{},limitUpPrice:{}", code, limitUpDate, period,
                    avg, limitUpPrice);
                continue;
            }
            ////买入价在涨停价下3.5%以上，换算一下均线在买入价4.57以上，跳过
            //if (BigDecimalUtils.isGt(avg, limitUpClosePrice)) {
            //    log.error("{}-{}-{} avg isGreatThan limitUpClosePrice,ignore,avg:{},limitUpClosePrice:{}", code,
            //        limitUpDate, period, avg, limitUpClosePrice);
            //    continue;
            //}
            //计算修正后的均线
            BigDecimal fixPrice = calculateAvgStockPrice(limitUpClosePrice, avg, period);
            BigDecimal fixAvg = getFixedAveragePrice(avg, limitUpClosePrice, fixPrice, period);
            if (BigDecimalUtils.isGe(fixPrice, limitDownPrice)) {
                log.info("{}-{}-{} avg isMatch price:{},avg:{},fixAvg:{}", code, limitUpDate, period, fixPrice, avg,
                    fixAvg
                );
                StockSupportItem supportItem = new StockSupportItem();
                supportItem.setPeriod(period);
                supportItem.setPrice(fixAvg);
                supportItem.setBuyPrice(BigDecimalUtils.multiply(fixAvg, BigDecimalUtils.onePointZeroOneTwo));
                supportItems.add(supportItem);
            }
        }
        if (CollectionUtils.isEmpty(supportItems)) {
            return null;
        }
        Collections.sort(supportItems, (o1, o2) -> o2.getPrice().compareTo(o1.getPrice()));
        //只有一条均线符合要求,就直接返回
        if (supportItems.size() == 1) {
            stockSupportDTO.setFirst(supportItems.get(0));
            return stockSupportDTO;
        }

        BigDecimal firstPrice = supportItems.get(0).getPrice();
        stockSupportDTO.setFirst(supportItems.get(0));
        List<StockSupportItem> firstItems = getMatchSupportItem(supportItems, firstPrice);
        if (CollectionUtils.isNotEmpty(firstItems)) {
            List<Integer> periods = firstItems.stream().map(StockSupportItem::getPeriod).collect(Collectors.toList());
            stockSupportDTO.getFirst().setJoinPeriod(StringUtils.join(periods, ","));
        }
        supportItems = supportItems.subList(firstItems.size(), supportItems.size());
        //第二支撑位
        if (CollectionUtils.isNotEmpty(supportItems)) {
            BigDecimal secondPrice = supportItems.get(0).getPrice();
            stockSupportDTO.setSecond(supportItems.get(0));
            List<StockSupportItem> secondItems = getMatchSupportItem(supportItems, secondPrice);
            if (CollectionUtils.isNotEmpty(secondItems)) {
                List<Integer> periods = secondItems.stream().map(StockSupportItem::getPeriod).collect(
                    Collectors.toList());
                stockSupportDTO.getSecond().setJoinPeriod(StringUtils.join(periods, ","));
            }
            supportItems = supportItems.subList(secondItems.size(), supportItems.size());
        }
        //第三支撑位
        if (CollectionUtils.isNotEmpty(supportItems)) {
            BigDecimal thirdPrice = supportItems.get(0).getPrice();
            stockSupportDTO.setThird(supportItems.get(0));
            List<StockSupportItem> thirdItems = getMatchSupportItem(supportItems, thirdPrice);
            if (CollectionUtils.isNotEmpty(thirdItems)) {
                List<Integer> periods = thirdItems.stream().map(StockSupportItem::getPeriod).collect(
                    Collectors.toList());
                stockSupportDTO.getThird().setJoinPeriod(StringUtils.join(periods, ","));
            }
        }
        return stockSupportDTO;
    }

    private static List<StockSupportItem> getMatchSupportItem(List<StockSupportItem> supportItems, BigDecimal price) {
        List<StockSupportItem> items = new ArrayList<>();
        for (int i = 0; i < supportItems.size(); i++) {
            StockSupportItem item = supportItems.get(i);
            BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(price, item.getPrice());
            if (BigDecimalUtils.isLe(rate, BigDecimalUtils.two)) {
                items.add(item);
                continue;
            }
            break;
        }
        return items;
    }

    private static BigDecimal calculateAvgStockPrice(BigDecimal limitUpPrice, BigDecimal avg, Integer period) {
        //计算差价平均,与均线相减
        BigDecimal priceRange = BigDecimalUtils.subtract(limitUpPrice, avg);
        priceRange = BigDecimalUtils.divide(priceRange, new BigDecimal(period));
        return BigDecimalUtils.subtract(avg, priceRange);
    }

    private static BigDecimal getFixedAveragePrice(BigDecimal avg, BigDecimal currentPrice, BigDecimal minPrice,
        Integer period) {
        //计算差价平均,与均线相减
        BigDecimal priceRange = BigDecimalUtils.subtract(currentPrice, minPrice);
        BigDecimal avgRange = BigDecimalUtils.divide(priceRange, new BigDecimal(period));
        return BigDecimalUtils.subtract(avg, avgRange);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        for (Integer period : ALL_PERIODS) {
            PERIOD_MAP.put(period, period + "日均线");
        }
        String stockImage = ClassUtils.getFileContentList("stockImageCode.json").get(0);
        JSONObject stockImageJson = JSON.parseObject(stockImage);
        winStockImageList = Lists.newArrayList(stockImageJson.getString("win").split(","));
        allWinStockImageList = Lists.newArrayList(stockImageJson.getString("allWin").split(","));
        lossStockImageList = Lists.newArrayList(stockImageJson.getString("loss").split(","));

    }

    private static String getTradeNumRatioCode(StockDayData beforeTwoData, StockDayData beforeOneData,
        StockDayData data, StockDayData afterOneData, StockDayData afterTwoData, int index) {
        int tradeNum = data.getTradeNum() / 2;
        int relativeBeforeTwoTradeNum = formatTradeNunm(beforeTwoData.getTradeNum() / tradeNum);
        int relativeBeforeOneTradeNum = formatTradeNunm(beforeOneData.getTradeNum() / tradeNum);
        int relativeDataTradeNum = formatTradeNunm(data.getTradeNum() / tradeNum);
        int relativeAfterOneTradeNum = formatTradeNunm(afterOneData.getTradeNum() / tradeNum);
        int relativeAfterTwoTradeNum = formatTradeNunm(afterTwoData.getTradeNum() / tradeNum);
        //第二日买入
        if (index == 1) {
            return "" + relativeBeforeTwoTradeNum + relativeBeforeOneTradeNum + relativeDataTradeNum
                + relativeAfterOneTradeNum;
        }
        //第三日买入
        return "" + relativeBeforeTwoTradeNum + relativeBeforeOneTradeNum + relativeDataTradeNum
            + relativeAfterOneTradeNum + relativeAfterTwoTradeNum;
    }

    private static int formatTradeNunm(int num) {
        if (num == 0) {
            return 1;
        }
        if (num > 8) {
            return 9;
        }
        return num;
    }

    public static void checkPreditction(StockSupportDTO stockSupport, StockDayData data, StockDayData afterData) {
        if (Objects.isNull(afterData)) {
            return;
        }
        stockSupport.setDate(afterData.getDate());
        stockSupport.setRealtimePrice(afterData.getClosePrice());
        stockSupport.setOpenPrice(afterData.getOpenPrice());
        stockSupport.setMinPrice(afterData.getMinPrice());
        int markEmotion = 9;
        int currentEmotion = 9;
        StockSupportCheckInfo checkInfo = checkSupport(stockSupport.getFirst(), 1, data, afterData, markEmotion,
            currentEmotion);
        if (BooleanUtils.isTrue(checkInfo.getMatchFlag())) {
            setMatchCheckInfo(stockSupport, checkInfo, afterData, markEmotion, currentEmotion);
            return;
        }
        if (Objects.nonNull(stockSupport.getSecond())) {
            checkInfo = checkSupport(stockSupport.getSecond(), 2, data, afterData, markEmotion, currentEmotion);
            if (BooleanUtils.isTrue(checkInfo.getMatchFlag())) {
                setMatchCheckInfo(stockSupport, checkInfo, afterData, markEmotion, currentEmotion);
                return;
            }
        }
        if (Objects.nonNull(stockSupport.getThird())) {
            checkInfo = checkSupport(stockSupport.getThird(), 3, data, afterData, markEmotion, currentEmotion);
            if (BooleanUtils.isTrue(checkInfo.getMatchFlag())) {
                setMatchCheckInfo(stockSupport, checkInfo, afterData, markEmotion, currentEmotion);
                return;
            }
        }
        //设置未匹配支撑位的信息
        stockSupport.setRemark(checkInfo.getRemark());
        stockSupport.setMatchFlag(checkInfo.getMatchFlag());

    }

    private static void setMatchCheckInfo(StockSupportDTO stockSupport, StockSupportCheckInfo checkInfo,
        StockDayData afterData, int markEmotion, int currentEmotion) {
        stockSupport.setSupportLevel(checkInfo.getSupportLevel());
        stockSupport.setRemark(checkInfo.getRemark());
        stockSupport.setMatchFlag(checkInfo.getMatchFlag());
        BigDecimal supportPrice = checkInfo.getSupportPrice();
        supportPrice = BigDecimalUtils.multiply(supportPrice, BigDecimalUtils.onePointZeroOneOne);
        stockSupport.setSupportPrice(supportPrice);
        stockSupport.setRealtimeRate(
            BigDecimalUtils.subStractAndDividePrecent(afterData.getClosePrice(), supportPrice));
    }

    private static StockSupportCheckInfo checkSupport(StockSupportItem supportItem,
        int index, StockDayData data,
        StockDayData afterData, int markEmotion, int currentEmotion) {
        BigDecimal supportPrice = supportItem.getPrice();
        BigDecimal closePrice = data.getClosePrice();
        BigDecimal basicPrice = BigDecimalUtils.divide(closePrice.add(afterData.getOpenPrice()), BigDecimalUtils.two);
        BigDecimal supportRate = BigDecimalUtils.subStractAndDividePrecent(closePrice, supportPrice);
        BigDecimal minRate = BigDecimalUtils.subStractAndDividePrecent(supportPrice,
            afterData.getMinPrice());
        //支撑位比昨日收盘价低3个点以上，支撑位有效
        BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(basicPrice, supportPrice);
        StockSupportCheckInfo checkInfo = new StockSupportCheckInfo();
        BigDecimal compareRate = getCompareRate(markEmotion, currentEmotion);
        BigDecimal supportCompareRate = getSupportCompareRate(markEmotion, currentEmotion);
        BigDecimal supportPriceRate = getSupportPriceRate(markEmotion, currentEmotion);
        if (BigDecimalUtils.isGe(supportRate, BigDecimalUtils.eightPointEight)) {
            checkInfo.setMatchFlag(BooleanUtils.FALSE);
            checkInfo.setRemark("第" + index + "支撑跌幅过大,与开盘价相差" + supportRate + "%");
        } else if (BigDecimalUtils.isLt(afterData.getOpenPrice(), supportPrice)) {
            //开盘价跌破支撑
            checkInfo.setMatchFlag(BooleanUtils.FALSE);
            checkInfo.setRemark("开盘价跌破第" + index + "支撑");
        } else if (BigDecimalUtils.isGe(rate, compareRate)) {
            if (BigDecimalUtils.isGe(minRate, supportCompareRate)) {
                checkInfo.setMatchFlag(BooleanUtils.TRUE);
                checkInfo.setSupportLevel(index);
                checkInfo.setSupportPrice(supportPrice);
                checkInfo.setRemark("第" + index + "支撑有效,与最低价相差" + minRate + "%");
            } else {
                checkInfo.setMatchFlag(BooleanUtils.FALSE);
                checkInfo.setRemark("第" + index + "支撑有效,但未到支撑,与最低价相差" + minRate + "%");
            }
        } else {
            checkInfo.setMatchFlag(BooleanUtils.FALSE);
            checkInfo.setRemark("第" + index + "支撑无效");
        }
        return checkInfo;
    }

    private static BigDecimal getCompareRate(int markEmotion, int currentEmotion) {
        if (StockEmotionUtils.isActiveEmotion(markEmotion) && StockEmotionUtils.isActiveEmotion(currentEmotion)) {
            Integer rate = 10 - currentEmotion;
            return BigDecimalUtils.of(rate);
        }
        return BigDecimalUtils.three;
    }

    private static BigDecimal getSupportCompareRate(int markEmotion, int currentEmotion) {
        if (StockEmotionUtils.isColdEmotion(markEmotion)) {
            if (StockEmotionUtils.isNegativeEmotion(currentEmotion)) {
                double rate = 1.9 - currentEmotion;
                return BigDecimalUtils.of(rate);
            }
        }
        if (StockEmotionUtils.isSensitiveEmotion(currentEmotion)) {
            return BigDecimalUtils.zeroPointFive;
        }
        return BigDecimalUtils.fuOnePointOne;
    }

    private static BigDecimal getSupportPriceRate(int markEmotion, int currentEmotion) {
        if (StockEmotionUtils.isColdEmotion(markEmotion)) {
            if (StockEmotionUtils.isNegativeEmotion(currentEmotion)) {
                double rate = 100 + currentEmotion - 1.9;
                return BigDecimalUtils.of(rate / 100);
            }
        }
        return BigDecimalUtils.onePointZeroOneOne;
    }

    public static void main(String[] args) {
        String s = "{\n" + "    \"earlyWarnFlag\": \"n\",\n" + "    \"matchDate\": \"2024-12-12 13:40:14\",\n"
            + "    \"code\": \"sh603015\",\n" + "    \"openMatchItems\": [\n" + "        {\n"
            + "            \"buyPrice\": 12.2,\n" + "            \"period\": 13,\n" + "            \"avg\": 12.06,\n"
            + "            \"rate\": 0,\n" + "            \"price\": 12.06,\n" + "            \"ignore\": false\n"
            + "        }\n" + "    ],\n" + "   \n" + "    \"match\": true,\n" + "    \"index\": 1,\n"
            + "    \"openPrice\": 12.06,\n" + "    \n" + "    \"matchItems\": [\n" + "\n" + "    ]\n" + "}";
        TldMatchInfo tldMatchInfo = JSON.parseObject(s, TldMatchInfo.class);
        TldMatchItem tldMatchItem = getMatchItem(tldMatchInfo.getOpenMatchItems(), tldMatchInfo.getMatchItems(),
            tldMatchInfo.getCrossItems(), tldMatchInfo.getOpenPrice());
        System.out.println(JSON.toJSONString(tldMatchItem));
    }
}
